﻿using LxBasic;
using LxCore;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace LxCFD
{
    /// <summary>
    /// GraphDisplay.xaml 的交互逻辑
    /// </summary>
    public partial class GraphDisplay : UserControl
    {
        public GraphDisplay()
        {
            InitializeComponent();
            _Instance = this;
            GraphViewport.Camera = camera = new OrthographicCamera()
            {
                LookDirection = CameraCal.LookDirection,
                UpDirection = CameraCal.UpDirection,
                Position = CameraCal.Position,
                Width = CameraCal.Width,
            };

            Model3DGroup ls = new Model3DGroup();
            ls.Children.Add(new AmbientLight(Color.FromRgb(100, 100, 100)));
            ls.Children.Add(lightFollowCamera = new DirectionalLight(Color.FromRgb(160, 160, 160), new Vector3D(0, 0, -1)));
            defaultLights = new ModelVisual3D() { Content = ls };
            SetCoordinateAndLight(CameraCal.LookDirection, CameraCal.UpDirection);
            ClearGraph();
        }
        private static GraphDisplay _Instance;
        public static GraphDisplay Instance => _Instance;
        protected OrthographicCamera camera;
        protected ModelVisual3D defaultLights;
        protected DirectionalLight lightFollowCamera;
        protected CameraCaler CameraCal = new CameraCaler();

        public static void DisplayGraph(ModelVisual3D model, ColorRange colorRange)
        {
            _Instance.ClearGraph();
            if (model == null)
            {
                LxLog.Warn("空白图像");
                return;
            }
            _Instance.AddGraph(model);
            _Instance.SetColorBar(colorRange);
        }
        protected void ClearGraph()
        {
            GraphViewport.Children.Clear();
            GraphViewport.Children.Add(defaultLights);
            CameraCal.SetBounds(Rect3D.Empty);
            SetColorBar(null);
        }
        protected void UpdateBounds(Rect3D newBound)
        {
            if (newBound == Rect3D.Empty) return;
            bool isLastEmpty = CameraCal.GraphBounds.IsEmpty;
            camera.Position = CameraCal.SetBounds(newBound);
            if (isLastEmpty)
            {
                (camera.Position, camera.Width) = CameraCal.Fit(VisualTreeHelper.GetDescendantBounds(this.GraphViewport), this.GraphViewport.RenderSize);
            }
        }

        protected void AddGraph(ModelVisual3D modelVisual3D)
        {
            GraphViewport.Children.Add(modelVisual3D);
            UpdateBounds(Rect3D.Union(CameraCal.GraphBounds, modelVisual3D.Content.Bounds));//, VisualTreeHelper.GetDescendantBounds(modelVisual3D))
        }
        protected void AddGraph(Model3DGroup model3DGroup)
        {
            GraphViewport.Children.Add(new ModelVisual3D() { Content = model3DGroup });
            UpdateBounds(Rect3D.Union(CameraCal.GraphBounds, model3DGroup.Bounds));
        }
        protected void SetColorBar(ColorRange colorRange)
        {
            ColorBarView.LoadColor(colorRange);
        }


        private Point mousePosLast;
        //private bool isMouseDown = false;//在标题栏双击，可能导致触发mousemove 的实践，加一变量判断是否是在grid里单机
        private MouseButton? __MouseButtonDown;
        protected MouseButton? MouseButtonDown
        {
            get => __MouseButtonDown;
            set
            {
                __MouseButtonDown = value;
                switch (value)
                {
                    case MouseButton.Left:
                        Cursor = Cursors.ScrollAll;
                        double len = double.MaxValue;
                        Point3D hit = new Point3D();
                        HitTestResultCallback hitcallback = (rawresult) =>
                        {
                            if (rawresult is RayHitTestResult rayResult)
                            {
                                Point3D h = rayResult.PointHit;
                                double l = Vector3D.DotProduct(h - CameraCal.Position, CameraCal.LookDirection);
                                if (l < len)
                                {
                                    len = l;
                                    hit = h;
                                }
                            }
                            return HitTestResultBehavior.Continue;
                        };
                        VisualTreeHelper.HitTest(GraphViewport, null, hitcallback, new PointHitTestParameters(mousePosLast));
                        CameraCal.RotateCenter = len == double.MaxValue ? CameraCal.GraphCenter : hit;
                        Vector3D cameraToRotateCenter = VectorMath.ProjectVectorToPlane(CameraCal.RotateCenter - CameraCal.Position, CameraCal.LookDirection);
                        Vector viewCenterToRotateCenter = (new Vector(Vector3D.DotProduct(cameraToRotateCenter, CameraCal.GetRight()), -Vector3D.DotProduct(cameraToRotateCenter, CameraCal.UpDirection))) / CameraCal.Width * GraphPanel.ActualWidth;
                        RotateIcon.Visibility = Visibility.Visible;
                        Canvas.SetLeft(RotateIcon, (GraphPanel.ActualWidth - RotateIcon.ActualWidth) / 2 + viewCenterToRotateCenter.X);
                        Canvas.SetTop(RotateIcon, (GraphPanel.ActualHeight - RotateIcon.ActualHeight) / 2 + viewCenterToRotateCenter.Y);
                        break;
                    case MouseButton.Middle:
                        Cursor = Cursors.SizeAll;
                        RotateIcon.Visibility = Visibility.Hidden;
                        break;
                    default:
                        Cursor = Cursors.Arrow;
                        RotateIcon.Visibility = Visibility.Hidden;
                        break;
                }
            }
        }
        private void ButtonFit_Click(object sender, RoutedEventArgs e)
        {
            (camera.Position, camera.Width) = CameraCal.Fit(VisualTreeHelper.GetDescendantBounds(this.GraphViewport), this.GraphViewport.RenderSize);
        }
        private void ButtonBigger_Click(object sender, RoutedEventArgs e)
        {
            camera.Width = CameraCal.Zoom(1.05);
        }
        private void ButtonSmaller_Click(object sender, RoutedEventArgs e)
        {
            camera.Width = CameraCal.Zoom(0.95);
        }
        private void ButtonSetView_Click(object sender, RoutedEventArgs e)
        {

        }
        private void ButtonTest_Click(object sender, RoutedEventArgs e)
        {

        }

        private void GridMouse_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            mousePosLast = e.GetPosition(GraphPanel);
            MouseButtonDown = e.ChangedButton;
        }
        private void GridMouse_MouseMove(object sender, MouseEventArgs e)
        {
            switch (MouseButtonDown)
            {
                case MouseButton.Left:
                    Point mousePosNow = e.GetPosition(GraphPanel);
                    (camera.LookDirection, camera.UpDirection, camera.Position) = CameraCal.Rotate(mousePosNow - mousePosLast, GraphPanel.ActualWidth);
                    SetCoordinateAndLight(CameraCal.LookDirection, CameraCal.UpDirection);
                    mousePosLast = mousePosNow;
                    break;
                case MouseButton.Middle:
                    Point mousePosNow2 = e.GetPosition(GraphPanel);
                    camera.Position = CameraCal.Move(mousePosNow2 - mousePosLast, GraphPanel.ActualWidth);
                    mousePosLast = mousePosNow2;
                    break;
                default: break;
            }
        }

        private void GridMouse_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            Point mp = e.GetPosition(GraphPanel);
            if (e.Delta > 0)
            {
                (camera.Position, camera.Width) = CameraCal.Zoom(1.1, mp, GraphPanel.RenderSize);
            }
            else if (e.Delta < 0)
            {
                (camera.Position, camera.Width) = CameraCal.Zoom(0.9, mp, GraphPanel.RenderSize);
            }
        }

        private void GridMouse_MouseUp(object sender, MouseButtonEventArgs e)
        {
            MouseButtonDown = null;
        }

        private void GridMouse_MouseLeave(object sender, MouseEventArgs e)
        {
            MouseButtonDown = null;
        }
        private void SetCoordinateAndLight(Vector3D lookDir, Vector3D upDir)
        {
            CoordinateSystem.SetCamera(lookDir, upDir);
            lightFollowCamera.Direction = lookDir - 0.2 * upDir;
        }
        protected class CameraCaler
        {
            public double Width = 1;
            public Point3D Position = new Point3D(0, 0, 1);
            public Vector3D UpDirection = new Vector3D(0, 1, 0);
            public Vector3D LookDirection = new Vector3D(0, 0, -1);

            public Rect3D GraphBounds = Rect3D.Empty;
            public Point3D GraphCenter = new Point3D(0, 0, 0);
            public double CameraMinDistance = 1;
            public Point3D RotateCenter = new Point3D(0, 0, 0);
            public void ResetCamera()
            {
                Width = 1;
                Position = new Point3D(0, 0, 1);
                UpDirection = new Vector3D(0, 1, 0);
                LookDirection = new Vector3D(0, 0, -1);
            }
            public void SetCamera(OrthographicCamera ocamera)
            {
                this.Width = ocamera.Width;
                this.Position = ocamera.Position;
                this.UpDirection = ocamera.UpDirection;
                this.LookDirection = ocamera.LookDirection;
            }
            public Point3D SetBounds(Rect3D newBound)
            {
                if (newBound == Rect3D.Empty)
                {
                    GraphBounds = newBound;
                    GraphCenter = new Point3D(0, 0, 0);
                    CameraMinDistance = 1;
                    RotateCenter = new Point3D(0, 0, 0);
                }
                else
                {
                    GraphBounds = newBound;
                    Vector3D boundSize = (Vector3D)GraphBounds.Size;
                    GraphCenter = GraphBounds.Location + 0.5 * boundSize;
                    CameraMinDistance = boundSize.Length * 3 + 0.2;//Camera的边界更大一点//小于0.2显示不正常
                    RotateCenter = GraphCenter;
                    AdjustPosition();
                }
                return Position;
            }

            public (Point3D Pos, double Width) Fit(Rect bound2, Size panelSize, double fitRate = 0.8)
            {
                if (bound2.IsEmpty) return (Position, Width);
                Vector boundCenter = (Vector)bound2.Location + (Vector)bound2.Size / 2;
                Vector mouseMove = ((Vector)panelSize / 2 - boundCenter);
                double xs = bound2.Width / panelSize.Width;
                double ys = bound2.Height / panelSize.Height;
                return (Move(mouseMove, panelSize.Width), Zoom(0.8 / Math.Max(xs, ys)));
            }
            public (Vector3D LookDir, Vector3D UpDir, Point3D Pos) Rotate(Vector mouseMove, double panelWidth, double angleFactor = 300)//mouse的移动距离除以Viewport的宽度，左上为原点，向下为Y，向右为X
            {
                Vector angle2 = angleFactor / panelWidth * mouseMove;
                Vector3D axis = -angle2.Y * GetRight() - angle2.X * UpDirection;
                if (axis.Length != 0)
                {
                    Quaternion q = new Quaternion(axis, axis.Length);
                    Matrix3D m3 = new Matrix3D();
                    m3.RotateAt(q, RotateCenter);
                    Position = m3.Transform(Position);
                    UpDirection = m3.Transform(UpDirection);
                    LookDirection = m3.Transform(LookDirection);
                    UpDirection.Normalize();
                    LookDirection.Normalize();
                    AdjustPosition();
                }
                return (LookDirection, UpDirection, Position);
            }
            public Point3D AdjustPosition()
            {
                double distanceNow = Vector3D.DotProduct(LookDirection, GraphCenter - Position);
                double distanceToMove = distanceNow - CameraMinDistance;
                Position += distanceToMove * LookDirection;
                return Position;
            }
            public Point3D Move(Vector mouseMove, double panelWidth)
            {
                Vector move2 = Width / panelWidth * mouseMove;
                Vector3D move3 = move2.Y * UpDirection - move2.X * GetRight();//camera位置与物体移动方向相反
                Position += move3;
                return Position;
            }
            public (Point3D Pos, double Width) Zoom(double factor, Point fixedPos, Size panelSize)
            {
                Vector cameraToZoomCenter2d = Width / panelSize.Width * ((Vector)fixedPos - (Vector)panelSize / 2);
                Vector3D cameraToZoomcenter3d = -cameraToZoomCenter2d.Y * UpDirection + cameraToZoomCenter2d.X * GetRight();
                //Point3D fixPos3 = Position + cameraToZoomcenter3d;
                //cameraToZoomcenter3d /= factor;
                //Position = fixPos3 - cameraToZoomcenter3d;
                Position += (1 - 1 / factor) * cameraToZoomcenter3d;
                return (Position, Zoom(factor));
            }
            public double Zoom(double factor)
            {
                Width /= factor;
                return Width;
            }
            public (Vector3D LookDir, Vector3D UpDir, Point3D Pos) SetView(Vector3D upDirection, Vector3D lookDirection)
            {
                upDirection.Normalize();
                lookDirection.Normalize();
                Point3D viewcenter = VectorMath.ProjectPointToPlane(Position, LookDirection, GraphCenter);
                Point3D newviewcenter = VectorMath.ProjectPointToPlane(Position, lookDirection, GraphCenter);
                Position += viewcenter - newviewcenter;
                UpDirection = upDirection;
                LookDirection = lookDirection;
                AdjustPosition();
                return (LookDirection, UpDirection, Position);
            }
            public Vector3D GetRight() => Vector3D.CrossProduct(LookDirection, UpDirection);
        }
    }

}
